package ch.usi.inf.pf2.alu;

import junit.framework.TestCase;
import ch.usi.inf.pf2.Sheet;
import ch.usi.inf.pf2.alu.nodes.Node;
import ch.usi.inf.pf2.alu.nodes.WrongNumberOfArgumentsException;
import ch.usi.inf.pf2.analyzer.UnrecognizedTokenException;
import ch.usi.inf.pf2.cell.Data;
import ch.usi.inf.pf2.cell.Value;

/**
 * 
 * @author Thomas Mantegazzi, Yotam Sharon
 *
 */

public class TestParser extends TestCase {
	
	private Sheet sheet;
	
	protected void setUp() {
		sheet = new Sheet();
		sheet.initializeSheet();
	}
	
	public void testIntNumber() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("223", "A1");
		// assert that we have no syntax errors
		assertNotNull(n); 
		Data v = n.evaluate();
		// assert that we have no runtime errors
		assertNotNull(v);
		// (implicitly) assert that we get a value of the correct type (cast would throw a ClassCastException otherwise)
		Value nv = (Value)v;
		// assert that we get the correct value
		assertEquals(223.0, nv.getInstanceDouble());
	}

	public void testFloatNumber() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("0.9", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(0.9, nv.getInstanceDouble());
	}

	public void testAddition() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException , SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("12+3.1", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(15.1, nv.getInstanceDouble());
	}

	public void testSubtraction() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("12-3", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(9.0, nv.getInstanceDouble());
	}

	public void testAdditionAndSubtraction() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("1-1-1", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(-1.0, nv.getInstanceDouble());
	}
	
	public void testOperationsFirst() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("-(-(-(+(-2))))", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(2.0, nv.getInstanceDouble());
	}
	
	public void testMultiplication() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("2*3*2", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(12.0, nv.getInstanceDouble());
	}
	
	public void testDivision() throws UnrecognizedTokenException, ParenthesisException , UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("6/3", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(2.0, nv.getInstanceDouble());
	}
	
	public void testMultiplicationAndDivision() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("2*3*2/6", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(2.0, nv.getInstanceDouble());
	}
	
	public void testPower() throws UnrecognizedTokenException, ParenthesisException , UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("2^3", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(8.0, nv.getInstanceDouble());
	}
	
	public void testPowerMultiplicationDivision() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("2^3*8/64", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(1.0, nv.getInstanceDouble());
	}
	
	public void testModulo() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("4%3", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(1.0, nv.getInstanceDouble());
	}
	
	public void testPrecedence() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("2-2*3+4", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(0.0, nv.getInstanceDouble());
	}
	
	public void testSuperMegaExpression() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("-(2 + (-4^(-2)) * 4) - 2/(-2)", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(-0.75, nv.getInstanceDouble());
	}
	
	public void testCellReference() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		sheet.insertDataAtCell("-2", 1, 1);
		sheet.insertDataAtCell("= 2/(-2)", 1, 2);
		Node n = p.parseFormula("-(2 + (-4^(-(-A1))) * 4) + (-A2)", "C3");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(-0.75, nv.getInstanceDouble());
	}
	
	public void testCellReferenceWithNegativeValue() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		sheet.insertDataAtCell("-2", 1, 1);
		Node n = p.parseFormula("-A1", "C3");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(2.0, nv.getInstanceDouble());
	}
	
	public void testIdentifiers() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		Node n = p.parseFormula("Sum(1,2,3)", "A1");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(6.0, nv.getInstanceDouble());
	}
	
	public void testExpressionComplete() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		sheet.insertDataAtCell("-2", 1, 1);
		Node n = p.parseFormula("sum(average(1,max(2,3)),1)+4^(sum(2,A1)) + sin("+Math.PI+"/2)", "A2");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(5.0, nv.getInstanceDouble());
	}
	
	public void testCellReferencesWithColon() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		sheet.insertDataAtCell("1", "A1");
		sheet.insertDataAtCell("1", "A2");
		sheet.insertDataAtCell("1", "B1");
		sheet.insertDataAtCell("1", "B2");
		Node n = p.parseFormula("Sum(A1:B2,B2:A1, A2:B1)", "D10");
		assertNotNull(n);
		Data v = n.evaluate();
		assertNotNull(v);
		Value nv = (Value)v;
		assertEquals(12.0, nv.getInstanceDouble());
	}
	
	public void testUnrecognizedTokenException() throws ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("= 3? + 2", "A1");
			assertTrue(false);
		} catch (UnrecognizedTokenException ex) {
			
		}
	}
	
	public void testParenthesisException() throws UnrecognizedTokenException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("=(1 + 2)))", "A1");
			assertTrue(false);
		} catch (ParenthesisException ex) {
			assertEquals("Error: missing 2 '('", ex.getMessage());
		}
	}
	
	public void testUnrecognizedIdentifer() throws UnrecognizedTokenException, ParenthesisException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("= bula(3)", "A1");
			assertTrue(false);
		} catch (UnrecognizedIdentifierException ex) {
			assertEquals("Error: unrecognized identifier at position 2: 'bula'", ex.getMessage());
		}
	}
	
	public void testSyntaxError() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, WrongNumberOfArgumentsException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("= 3 +", "A1");
			assertTrue(false);
		} catch (SyntaxException ex) {
			assertEquals("Error: syntax error", ex.getMessage());
		}
	}
	
	public void testWrongNumberOfArgumentsException() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, CircularReferenceException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("= cos(1,2,3)", "A1");
			assertTrue(false);
		} catch (WrongNumberOfArgumentsException ex) {
			assertEquals("Error: 'cos()' expect only one argument: given <3> arguments", ex.getMessage());
		}
	}
	
	public void testCircularReferenceException() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, MissingParenthesisException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		sheet.insertDataAtCell("=A1", "A2");
		try {
			p.parseFormula("= A2", "A1");
			assertTrue(false);
		} catch (CircularReferenceException ex) {
			assertEquals("Error: circular reference", ex.getMessage());
		}
	}
	
	public void testMissingParenthesisException() throws UnrecognizedTokenException, ParenthesisException, UnrecognizedIdentifierException, SyntaxException, WrongNumberOfArgumentsException, CircularReferenceException, ReferenceToFormulaWithErrorException {
		Parser p = new Parser(sheet);
		try {
			p.parseFormula("sum", "A1");
			assertTrue(false);
		} catch (MissingParenthesisException ex) {
			assertEquals("Error: missing parenthesis '(..)'", ex.getMessage());
		}
	}
	
}
